package com.pinfly.common.util;

/**
 * StringBuffer like class that supports creating XML documents. This class
 * provides methods that simplify creating XML documents. <h3>
 * Examples</h3> To add an element with several attributes but not child
 * elements to an XMLBuffer: <code><pre>
 *  XLMBuffer buff;
 *  ...
 *  buff.startElement ("MyTag")
 *      .addAttribute ("Attr1", "value1")
 *      .addAttribute ("Attr2", "value2")
 *      .addAttribute ("Attr3", "value3")
 *      .endElement ();
 * 	 </pre></code> Note that chaining the calls together produces smaller and
 * faster code than the logically equivalent <code><pre>
 *  buff.startElement ("MyTag");
 *  buff.addAttribute ("Attr1", "value1");
 *  buff.addAttribute ("Attr2", "value2");
 *  buff.addAttribute ("Attr3", "value3");
 *  buff.endElement ();
 * 	 </pre></code> It is not always possible to chain together calls in this way
 * (for example some attributes may be optional and so are guarded by
 * <code>if</code> statements) but where possible it is good practice to do
 * this.
 * <p>
 * The following example adds an element with both attributes and a child
 * element. <code><pre>
 *  XMLBuffer buff;
 *  ...
 *  buff.startElement ("Parent")
 *      .addAttribute ("parentAttr", "parentValue")
 *      .endStart()
 *      .startElement ("Child")
 *      .addAttribute ("childAttr", "childValue")
 *      .endElement ()
 *      .endElement ("Parent");
 * 	 </pre></code>
 * <p>
 * Finally, here is an example of an element that includes a body <code><pre>
 *  buff.startElement ("Body")
 *      .closeElement ()
 *      .append ("Here is some text for the body")
 *      .finishElement ("Body");
 * 	 </pre></code>
 * <p>
 * The text values used in <code>append</code> and <code>addAttribute</code> are
 * checked for any characters that are special to XML, and if any are found they
 * are replaced by their appropriate entity references.
 */
public class XMLBuffer
{
    /**
     * String value used for boolean attributes that are <code>true</code>. The
     * value is equal to <code>java.lang.Boolean.TRUE.toString()</code>
     */
    public static final String TRUE = Boolean.TRUE.toString ();
    /**
     * String value used for boolean attributes that are <code>false</code>. The
     * value is equal to <code>java.lang.Boolean.FALSE.toString()</code>
     */
    public static final String FALSE = Boolean.FALSE.toString ();
    /** XML Document preamble */
    public static final String PREAMBLE = "<?xml version=\"1.0\"?>";

    /**
     * The <code>StringBuffer</code> containing the document. It is initialized
     * to a moderate size to avoid a lot of small reallocations immediately
     * after its creation
     */
    private StringBuffer m_buff = new StringBuffer (500);

    /**
     * Create a new XMLBuffer. If <code>addPreamble</code> is <code>true</code>
     * then the buffer will be intialized with the standard XML document
     * preamble:<br>
     * <code>&lt;?xml version=\"1.0\"?&gt;</code><br>
     * else the buffer will be empty.
     * 
     * @param addPreamble if <code>true</code> then the buffer will include the
     *            preamble.
     */
    public XMLBuffer (boolean addPreamble)
    {
        if (addPreamble)
        {
            m_buff.append (PREAMBLE);
        }
    }

    /**
     * Create a new XMLBuffer with a preamble. This is equivalent to
     * <code>XMLBuffer (true)</code>.
     */
    public XMLBuffer ()
    {
        this (true);
    }

    /**
     * Create a new XMLBuffer and start an element. This is a convenience
     * function that creates a new XMLBuffer with a preamble and then calls
     * {@link #startElement startElement} to begin the element.
     * 
     * @param name the name of the element
     */
    public XMLBuffer (String name)
    {
        startElement (name);
    }

    /**
     * Begins the start tag for an element. This appends a "<code>&lt;</code>"
     * followed by the element name.
     * 
     * @param name the name of the element
     * @return the buffer
     */
    public XMLBuffer startElement (String name)
    {
        m_buff.append ("<").append (name);
        return this;
    }

    /**
     * Ends the start tag for an element. This appends a "<code>&gt;</code>" to
     * the buffer. The element will have to be ended with a subsequent call to
     * {@link #endElement(String) endElement}.
     * 
     * @return the buffer
     */
    public XMLBuffer endStart ()
    {
        m_buff.append (">");
        return this;
    }

    /**
     * Finishes the start/end tag for an empty element. This appends a "
     * <code>/&gt</code>" to the buffer.
     * 
     * @return the buffer
     */
    public XMLBuffer endElement ()
    {
        m_buff.append ("/>");
        return this;
    }

    /**
     * Adds an element end tag to the buffer. This appends "
     * <code>&lt;/name&gt;</code>" to the buffer.
     * 
     * @param name the name of the element being closed
     * @return the buffer
     */
    public XMLBuffer endElement (String name)
    {
        m_buff.append ("</").append (name).append (">");
        return this;
    }

    /**
     * Add a <code>String</code> attribute to an element start tag. This adds
     * <code>attrName</code>="<code>attrValue</code>" to the buffer. Any
     * characters in <code>attrValue</code> that are special to XML are replaced
     * by the appropriate entity references before being added.
     * 
     * @param attrName the name of the attribute
     * @param attrValue the <code>String</code> value of the attribute
     * @return the buffer
     */
    public XMLBuffer addAttribute (String attrName, String attrValue)
    {
        m_buff.append (" ").append (attrName).append ("=\"");
        escapeXML (attrValue);
        m_buff.append ("\"");
        return this;
    }

    /**
     * Add a <code>boolean</code> attribute to an element start tag. The value
     * for the attribute will be either <code>TRUE</code> or <code>FALSE</code>
     * depending on <code>attrValue</code>
     * 
     * @param attrName the name of the attribute
     * @param attrValue the <code>boolean</code> value of the attribute
     * @return the buffer
     */
    public XMLBuffer addAttribute (String attrName, boolean attrValue)
    {
        return addAttribute (attrName, attrValue ? TRUE : FALSE);
    }

    /**
     * Add an <code>int</code> attribute to an element start tag.
     * 
     * @param attrName the name of the attribute
     * @param attrValue the <code>int</code> value of the attribute
     * @return the buffer
     */
    public XMLBuffer addAttribute (String attrName, int attrValue)
    {
        String value = "" + attrValue;
        return addAttribute (attrName, value);
    }

    /**
     * Add a <code>float</code> attribute to an element start tag.
     * 
     * @param attrName the name of the attribute
     * @param attrValue the <code>float</code> value of the attribute
     * @return the buffer
     */
    public XMLBuffer addAttribute (String attrName, float attrValue)
    {
        String value = "" + attrValue;
        return addAttribute (attrName, value);
    }

    /**
     * Add a <code>char</code> attribute to an element start tag. The character
     * is added as a single character text string, not its numeric value. If it
     * is a character that is special to XML, it is replaced by the appropriate
     * entity reference.
     * 
     * @param attrName the name of the attribute
     * @param attrValue the <code>char</code> value of the attribute
     * @return the buffer
     */
    public XMLBuffer addAttribute (String attrName, char attrValue)
    {
        char[] c = new char[1];
        c[0] = attrValue;
        String value = new String (c);
        return addAttribute (attrName, value);
    }

    /**
     * Add text to the buffer. Any XML reserved characters are replaced by their
     * corresponding entity references.
     * 
     * @param s the <code>String</code> to be added
     * @return the buffer
     * @see #appendRaw
     */
    public XMLBuffer append (String s)
    {
        escapeXML (s);
        return this;
    }

    /**
     * Add text to the buffer. No processing is done on the text before it is
     * added
     * 
     * @param s the <code>String</code> to be added
     * @return the buffer
     * @see #append
     */
    public XMLBuffer appendRaw (String s)
    {
        m_buff.append (s);
        return this;
    }

    /**
     * Return the contents of the buffer
     * 
     * @return the current contents of the buffer
     */
    public String toString ()
    {
        return m_buff.toString ();
    }

    /**
     * escapeXML
     * 
     * Append the specified to string to the string buffer, replacing XML
     * reserved characters (<, >, &, ', and ") in the string with their
     * respective entity references.
     * 
     * @param str The string to append
     * 
     * @return The modified StringBuffer
     */
    private void escapeXML (String str)
    {
        if (str != null)
        {
            int length = str.length ();
            for (int i = 0; i < length; i++)
            {
                char c = str.charAt (i);
                switch (c)
                {
                case '<':
                    m_buff.append ("&lt;");
                    break;

                case '>':
                    m_buff.append ("&gt;");
                    break;

                case '&':
                    m_buff.append ("&amp;");
                    break;

                case '\'':
                    m_buff.append ("&apos;");
                    break;

                case '"':
                    m_buff.append ("&quot;");
                    break;

                case '\n':
                    m_buff.append ("&#10;");
                    break;

                default:
                    m_buff.append (c);
                    break;
                }
            }
        }
    }

}
